package cn.aws360.boot.base.api.exception.handler;

import cn.aws360.boot.base.api.exception.ServiceException;
import cn.aws360.boot.base.api.pojo.response.ResultModel;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.nio.file.AccessDeniedException;
import java.util.List;

/**
 * @author junnan
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 请求参数缺失
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResultModel<String> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        String parameterName = e.getParameterName();
        String parameterType = e.getParameterType();
        log.error("请求参数缺失, parameterName: {}, parameterType: {}", parameterName, parameterType, e);
        return ResultModel.fail("请求参数缺失!");
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultModel<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        String method = e.getMethod();
        log.error("请求方式不支持, method: {}", method, e);
        return ResultModel.fail("请求方式不支持!");
    }

    /**
     * 请求参数校验失败，拦截 @Valid 校验失败的情况
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultModel<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String message = getArgNotValidMessage(e.getBindingResult());
        log.error("请求参数校验失败, message: {}", message, e);
        return ResultModel.fail(message);
    }

    /**
     * 请求参数校验失败，拦截 @Validated 校验失败的情况
     * 两个注解 @Valid 和 @Validated 区别是后者可以加分组校验，前者没有分组校验
     */
    @ExceptionHandler(BindException.class)
    public ResultModel<String> handleBindException(BindException e) {
        String message = getArgNotValidMessage(e.getBindingResult());
        log.error("请求参数校验失败, message: {}", message, e);
        return ResultModel.fail(message);
    }

    /**
     * 权限校验异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResultModel<String> handleAccessDeniedException(AccessDeniedException e) {
        String message = e.getMessage();
        log.error("权限校验异常, message: {}", message, e);
        return ResultModel.fail(message);
    }

    /**
     * 业务处理异常
     */
    @ExceptionHandler(ServiceException.class)
    public ResultModel<String> handleServiceException(ServiceException e) {
        log.error("业务处理异常, message: {}", e.getUserTip(), e);
        return ResultModel.res(Integer.parseInt(e.getErrCode()), e.getUserTip(), null);
    }

    /**
     * 未知的运行异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResultModel<String> handleRuntimeException(RuntimeException e) {
        e.printStackTrace();
        return ResultModel.error();
    }

    /**
     * 未知的系统异常
     */
    @ExceptionHandler(Exception.class)
    public ResultModel<String> handleException(Exception e) {
        log.error("未知的系统异常, message: {}", e.getMessage(), e);
        return ResultModel.error();
    }

    private String getArgNotValidMessage(BindingResult bindingResult) {
        if (ObjectUtil.isNull(bindingResult)) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        List<ObjectError> allErrorInfos = bindingResult.getAllErrors();
        for (ObjectError error : allErrorInfos) {
            stringBuilder.append(StrUtil.COMMA).append(error.getDefaultMessage());
        }
        return StrUtil.removePrefix(stringBuilder.toString(), StrUtil.COMMA);
    }

}